package github.sf.fw.tools.cmd;


import github.sf.fw.utils.ExceptionUtils;
import github.sf.fw.utils.StrUtil;
import github.sf.fw.utils.SystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;

public class ShellProcessor implements Closeable {
    private final static Logger logger = LoggerFactory.getLogger(ShellProcessor.class);
    private Process process;
    private String processId;
    private String directory;
    /**
     * 日志输出流
     */
    private OutputStream outputStream;

    private Queue<String> logContentQueue = new LinkedBlockingQueue<>();

    public void startWithFull(String workDirectory, String fullCommand) {
        start(workDirectory, Arrays.asList(fullCommand.split(" ")));
    }

    public void start(String workDirectory, List<String> commands) {
//        this.directory = directory;
        ProcessBuilder processBuilder = new ProcessBuilder();
        //合并错误输出到InputStream
        processBuilder.redirectErrorStream(true);
        if (!StrUtil.isEmpty(workDirectory)) {
            processBuilder.directory(new File(workDirectory));
        }
        processBuilder.command(commands);
        processBuilder.environment();
        try {
            this.process = processBuilder.start();
            printCommand(commands);
            this.processId = getProcessId();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void processOutput() {
        System.out.println("=====================terminal output start " + processId + "===================");
        ExceptionUtils.defaultVoid(() -> {
            logInputStream(process, process.getInputStream());
        });
        ExceptionUtils.defaultVoid(process::waitFor);
        System.out.println("=====================terminal output end ===================");
    }


    private void logInputStream(Process process, InputStream inputStream) throws IOException {
//        int count = 0;
        byte[] buff = new byte[1024];
        int len = -1;
        outputStream = new ByteArrayOutputStream();
        while (process.isAlive() && (len = inputStream.read(buff)) != -1) {
            outputStream.write(buff, 0, len);
            String s1 = new String(buff, 0, len, getCharset());
            logContentQueue.offer(s1);
            System.out.print(s1);
        }
    }

    /**
     * 保存文件
     */
    public String saveToLogFile(String fileName) {
        String logFilePath = fileName;
        if (StrUtil.isEmpty(logFilePath)) {
            fileName = UUID.randomUUID().toString();
            if (StrUtil.isEmpty(directory)) {
                directory = System.getProperty("user.dir");
            }
            logFilePath = StrUtil.fsCombine(directory, fileName) + ".log";
        }
        if (outputStream != null && outputStream instanceof ByteArrayOutputStream) {
            File file = new File(logFilePath);
            AccessController.<Boolean>doPrivileged((PrivilegedAction<Boolean>) () -> {
                if (!file.exists()) {
                    File parentFile = file.getParentFile();
                    if (!parentFile.exists()) {
                        boolean mkdirs = parentFile.mkdirs();
                    }
                    try {
                        return file.createNewFile();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
                return true;
            });
            try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
                String s = ((ByteArrayOutputStream) outputStream).toString(getCharset());
                fileOutputStream.write(s.getBytes(StandardCharsets.UTF_8));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            String s = outputStream.toString();
            return StrUtil.lastLine(s, 100);
        }
        return "";
    }

    private void printCommand(List<String> commands) {
        logger.info("task run command: {}", String.join(" ", commands));
    }

    public Process getProcess() {
        return process;
    }

    /**
     * get process id
     *
     * @return process id
     */
    public String getProcessId() {
        String processId = "0";
        String pidFieldName = "pid";
        if (SystemUtils.isWindows()) {
            pidFieldName = "handle";
        } else if (SystemUtils.isLinux()) {
            pidFieldName = "pid";
        } else {
            logger.warn("暂不支持{}类型的操作系统", SystemUtils.getOsName());
            return "0";
        }
        try {
            Field f = process.getClass().getDeclaredField(pidFieldName);
            f.setAccessible(true);

            processId = f.get(process).toString();
            f.setAccessible(false);
        } catch (Throwable e) {
            logger.error("Get task pid failed", e);
        }

        return processId;
    }

    private String getCharset() {
        if (SystemUtils.isWindows()) {
            return "GBK";
        } else if (SystemUtils.isLinux()) {
            return "utf-8";
        } else {
            logger.warn("暂不支持{}类型的操作系统", SystemUtils.getOsName());
            return "utf-8";
        }
    }

    public Queue<String> getLogContentQueue() {
        return logContentQueue;
    }

    @Override
    public void close() throws IOException {
        if (process != null) {
            process.destroyForcibly();
            process = null;
        }
        if (outputStream != null) {
            outputStream.close();
            outputStream = null;
        }
        logContentQueue.clear();
        logContentQueue = null;
    }
}
